﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection.Emit;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Diagnostics;
using System.Linq.Expressions;

namespace BReusable
{
    public static class BEmitMapper
    {
        public static ModuleBuilder ModuleBuilder(string assemblyName, string moduleName)
        {
            var an = new AssemblyName(assemblyName);
            var assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(an, AssemblyBuilderAccess.Run);
            return assemblyBuilder.DefineDynamicModule(moduleName);
        }

        public static void CreateProperty<TSource, TValue>(this TypeBuilder typeBuilder,
            Expression<Func<TSource, object>> memberExpression)
        {
            CreateProperty(typeBuilder,BReusable.Member.Name<TSource>(memberExpression),typeof(TValue));
        }


        public static void CreateProperty(TypeBuilder typeBuilder, string name, Type typ)
        {
            string field = "_" + name;//.ToLower();  
            FieldBuilder fieldBldr = typeBuilder.DefineField(field, typ, FieldAttributes.Private);
            PropertyBuilder propBldr = typeBuilder.DefineProperty(name, PropertyAttributes.None, typ, null);
            MethodAttributes getSetAttr = System.Reflection.MethodAttributes.Public
       | System.Reflection.MethodAttributes.Virtual
       | System.Reflection.MethodAttributes.Final
       | System.Reflection.MethodAttributes.HideBySig
       | System.Reflection.MethodAttributes.NewSlot;


            MethodBuilder getPropBldr = typeBuilder.DefineMethod("get_" + name, getSetAttr, typ, Type.EmptyTypes);

            ILGenerator getIL = getPropBldr.GetILGenerator();
            getIL.Emit(OpCodes.Ldarg_0);
            getIL.Emit(OpCodes.Ldfld, fieldBldr);
            getIL.Emit(OpCodes.Ret);
            MethodBuilder setPropBldr = typeBuilder.DefineMethod("set_" + name, getSetAttr, null, new Type[] { typ });
            ILGenerator setIL = setPropBldr.GetILGenerator();

            setIL.Emit(OpCodes.Ldarg_0);
            setIL.Emit(OpCodes.Ldarg_1);
            setIL.Emit(OpCodes.Stfld, fieldBldr);
            setIL.Emit(OpCodes.Ret);
            propBldr.SetGetMethod(getPropBldr);
            propBldr.SetSetMethod(setPropBldr);
        }
        public static T GenerateInterfaceDTO<T>()
        { return GenerateInterfaceDTO<T>(AssemblyBuilderAccess.Run); }

        public static T GenerateInterfaceDTO<T>(AssemblyBuilderAccess assemblyBuilderAccess)
        {
            AppDomain.CurrentDomain.DefineDynamicAssembly(new AssemblyName("BEmitTypes"), assemblyBuilderAccess);
            var moduleBuilder = ModuleBuilder("BEmitDynamic", "MainModule");

            var typeBuilder = moduleBuilder.DefineType("Proxy" + typeof(T).Name);
            foreach (var item in typeof(T).GetProperties())
            {
                CreateProperty(typeBuilder, item.Name, item.PropertyType);
            }
            if (typeof(T).IsInterface)
                typeBuilder.AddInterfaceImplementation(typeof(T));
            var type = typeBuilder.CreateType();
            return (T)Activator.CreateInstance(type);
        }

        public static MethodBuilder GenerateSetter<T>(TypeBuilder type, string name)
        {

            return type.DefineMethod("set_" + name, MethodAttributes.Public);
        }

        /// <summary>
        /// Uses T to read return type
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="type"></param>
        /// <param name="name"></param>
        /// <returns></returns>
        public static MethodBuilder GenerateGetter<T>(TypeBuilder type, string name)
        {
            // Declaring method builder
            // Method attributes
            System.Reflection.MethodAttributes methodAttributes =
                  System.Reflection.MethodAttributes.Public
                | System.Reflection.MethodAttributes.Virtual
                | System.Reflection.MethodAttributes.Final
                | System.Reflection.MethodAttributes.HideBySig
                | System.Reflection.MethodAttributes.NewSlot;
            MethodBuilder method = type.DefineMethod("get_" + name, methodAttributes);
            // Preparing Reflection instances
            ConstructorInfo ctor1 = typeof(CompilerGeneratedAttribute).GetConstructor(
                BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic,
                null,
                new Type[]{
            },
                null
                );
            var field = GenerateField<T>(type, name);

            // Adding custom attributes to method
            // [CompilerGeneratedAttribute]
            method.SetCustomAttribute(
                new CustomAttributeBuilder(
                    ctor1,
                    new Type[] { }
                    )
                );
            // Setting return type
            method.SetReturnType(typeof(T));
            // Adding parameters
            ILGenerator gen = method.GetILGenerator();
            // Preparing locals
            LocalBuilder num = gen.DeclareLocal(typeof(T));
            // Preparing labels
            Label label9 = gen.DefineLabel();
            // Writing body
            gen.Emit(OpCodes.Ldarg_0);
            gen.Emit(OpCodes.Ldfld, (FieldInfo)field);
            gen.Emit(OpCodes.Stloc_0);
            gen.Emit(OpCodes.Br_S, label9);
            gen.MarkLabel(label9);
            gen.Emit(OpCodes.Ldloc_0);
            gen.Emit(OpCodes.Ret);
            // finished

            return method;

        }

        public static FieldBuilder GenerateField<T>(TypeBuilder type, string name)
        {
            FieldBuilder field = type.DefineField(
       "<" + name + ">k__BackingField",
       typeof(T),
         FieldAttributes.Private
       );
            return field;

        }
    }
}
